/*
 * data_path.v
 *
 * Copyright 2024 dh33ex <dh33ex@riseup.net>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA or visit <http://www.gnu.org/licenses/>.
 *
 *
 */

module data_path(
    input               i_clk,
    input               i_rst,

    input               i_lnk_trig,
    input               i_reg1_we,
    input               i_B_src,

    input      [1:0]    i_reg1_src,
    input      [1:0]    i_PC_src,
    input      [3:0]    i_ALU_sel,

    output              o_ram_we,
    output     [3:0]    o_NZVC,

    input      [31:0]   i_instr,
    output     [31:0]   o_PC,

    input      [31:0]   i_ram_rd,
    output     [31:0]   o_ram_addr,
    output     [31:0]   o_ram_wd
);

    wire [3:0]  reg1_a;
    wire [3:0]  reg2_a;
    wire [3:0]  reg3_a;

    reg  [31:0] reg1_wd;
    wire [31:0] reg2_rd;
    wire [31:0] reg3_rd;

    wire [23:0] pc_offset;

    wire [3:0]  alu_NZVC;
    wire [31:0] alu_res;
    reg  [31:0] alu_b;

    pc pc(
        .i_clk(i_clk),
        .i_rst(i_rst),
        .i_reg3(reg3_rd),
        .i_offset(pc_offset),
        .i_PC_sel(i_PC_src),
        .o_PC(o_PC)
    );

    reg_file reg_file(
        .i_clk(i_clk),
        .i_rst(i_rst),
        .i_reg1_a(reg1_a),
        .i_reg2_a(reg2_a),
        .i_reg3_a(reg3_a),
        .i_reg1_we(i_reg1_we),
        .i_reg1_wd(reg1_wd),
        .i_PC(o_PC),
        .i_lnk_trig(i_lnk_trig),
        .o_reg1_rd(o_ram_wd),
        .o_reg2_rd(reg2_rd),
        .o_reg3_rd(reg3_rd)
    );

    alu alu(
        .i_A(reg2_rd),
        .i_B(alu_b),
        .i_ALU_sel(i_ALU_sel),
        .o_NZVC(o_NZVC),
        .o_ALU_result(alu_res)
    );

    always @(i_reg1_src or i_instr or alu_res or i_ram_rd or reg3_rd) begin: REG1_SRC_MUX
        case(i_reg1_src)
            2'b00: reg1_wd = {{16{i_instr[28]}}, i_instr[15:0]};        /* from instruction */
            2'b01: reg1_wd = alu_res;                                   /* from ALU */
            2'b10: reg1_wd = i_ram_rd;                                  /* from RAM */
            2'b11: reg1_wd = reg3_rd;                                   /* from reg3 */
            default: reg1_wd = 32'hxxxxxxxx;
        endcase
    end

    always @(i_B_src or i_instr or reg3_rd) begin: B_SRC_MUX
        case (i_B_src)
            1'b0: alu_b = reg3_rd;                                     /* from reg3 */
            1'b1: alu_b = {{16{i_instr[28]}}, i_instr[15:0]};          /* from im field */
            default: alu_b = 32'hxxxxxxxx;
        endcase
    end

    assign reg1_a = i_instr[27:24];
    assign reg2_a = i_instr[23:20];
    assign reg3_a = i_instr[3:0];

    assign pc_offset = i_instr[23:0];
    assign o_ram_addr = reg2_rd + {{12{i_instr[19]}}, i_instr[19:0]};

endmodule
